package com.mlh.mlhspringmvc.context;

import com.mlh.mlhspringmvc.annotation.AutoWired;
import com.mlh.mlhspringmvc.annotation.Controller;
import com.mlh.mlhspringmvc.annotation.Service;
import com.mlh.mlhspringmvc.xml.XMLParser;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

// MlhWebApplicationContext 表示我们自己的spring容器
public class MlhWebApplicationContext {
    //用list承接所有的类的全路径
    private ArrayList<String> classFullPathList = new ArrayList<>();
    //ioc容器
    public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
    private String configLocation;

    //无参构造器
    public MlhWebApplicationContext() {
    }

    public MlhWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }

    //初始化自定义的 spring 容器, 目标就是把要@Controller 等等初始化到容器中
    public void init() {
        // 通过web.xml文件来获取spring容器的配置文件
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);//获得需要注解扫描的包
        //如果需要扫描多个包 利用,将每个包装入数组 因此遍历扫描
        String[] basePackages = basePackage.split(",");
        if (basePackages.length > 0) {//没有需要扫描的包则跳过
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        //将扫描的类装入ioc容器
        executeInstance();
        //完成注入的bean对象,的属性的装配
        executeAutoWired();
    }

    public void scanPackage(String pack) {
        // 老韩解读
        // 1. pack 形式如: com.hspedu.controller
        // 2. 需要得到真正执行的类形式为D:/javaProject/mlh-springmvc/target/mlh-springmvc/WEB-INF/classes/com/mlh/controller/
        URL url = this.getClass()
                .getClassLoader()
                .getResource("/" + pack.replaceAll("\\.", "/"));//为什么getClassLoader.getResource（）方法能带"/"
        //因为貌似是因为web工程启动的原因  具体原因不知道为什么
        // 3. 可以测试输出 url , 注意这个测试，需要走 tomcat , 不能走 Test, 否则 url为 null
        //System.out.println("url=" + url);
        File file = new File(url.getFile());
        //遍历文件夹
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
                //如果是个文件夹则递归扫描
                scanPackage(pack + "." + f.getName());
            } else {
                //此时扫描到的文件，可能是.class，也可能是其他文件
                //.class文件还需要通过反射来确定是否有注解，在这里完成不了，因此全都添加到list容器中，后续再去处理
                classFullPathList.add(pack + "." + f.getName().replaceAll(".class", ""));
            }
        }
    }

    //编写方法，将扫描到的类，在满足条件的情况下，反射到ioc容器
    public void executeInstance() {
        // 判断是否扫描到类
        if (classFullPathList.size() == 0) {
            return;
        }
        try {
            for (String s : classFullPathList) {
                Class<?> clazz = Class.forName(s);
                //判断是否有@Controller
                if (clazz.isAnnotationPresent(Controller.class)) {
                    // 类名首字母小写作为id
                    String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
                            clazz.getSimpleName().substring(1);
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service service = clazz.getAnnotation(Service.class);
                    Object bean = clazz.newInstance();//为了保证是一个对象，在这里就先创建一个bean，以确保是对同一对象的引用
                    if (service.value().equals("")) {//value值为空 则用类名 接口名作为bean的id  但注意有多个id都要指向同一对象
                        //类名首字母小写
                        String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
                                clazz.getSimpleName().substring(1);
                        ioc.put(beanName, bean);
                        //获得该类的所有接口
                        Class<?>[] interfaces = clazz.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            //接口名 首字母小写
                            beanName = anInterface.getSimpleName().substring(0, 1).toLowerCase() +
                                    anInterface.getSimpleName().substring(1);
                            ioc.put(beanName, bean);
                        }
                    } else {
                        //利用value的值作为类名
                        String beanName = service.value();
                        ioc.put(beanName, bean);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 编写方法，完成属性的自动装配
    public void executeAutoWired() {
        // 判断ioc有没有要装配的对象
        if (ioc.isEmpty()) {
            return; // 你也可以抛出异常throw new RuntimeException("ioc 容器没有bean对象")
        }
        // 遍历ioc容器中的所有注入的bean对象，然后 获取到bean的所有字段/属性，判断是否需要
        // 装配
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            String key = entry.getKey();
            Object bean = entry.getValue();
            // 得到bean的所有字段/属性
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                // 判断当前这个字段是否有@AutoWired
                if (declaredField.isAnnotationPresent(AutoWired.class)) {
                    AutoWired annotation = declaredField.getAnnotation(AutoWired.class);
                    String beanName = annotation.value();
                    if (beanName.equals("")) {// 如果注解的value为空 则用类型注入
                        Class<?> type = declaredField.getType();
                        //获得类型首字母小写作为id
                        beanName = type.getSimpleName().substring(0, 1).toLowerCase() +
                                type.getSimpleName().substring(1);
                    }
                    if (null == ioc.get(beanName)) {// 说明你指定的名字对应的bean不在ioc容器
                        throw new RuntimeException("ioc容器中，不存在你要装配的bean");
                    }
                    // 防止装配的属性是privute，我们需要暴力破解
                    declaredField.setAccessible(true);
                    // 可以装配属性
                    try {
                        declaredField.set(bean, ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
